package com.gitee.srurl.snow.gen;

import cn.hutool.core.date.SystemClock;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.StrUtil;
import com.gitee.srurl.register.DataReportHandle;
import com.gitee.srurl.register.IDataReport;
import com.gitee.srurl.snow.backwards.AbstractBackwards;
import com.gitee.srurl.snow.backwards.ClockBackwardsFactory;
import com.gitee.srurl.snow.domain.SnowflakeConstant;
import com.gitee.srurl.snow.domain.SnowflakeData;
import com.gitee.srurl.snow.domain.SnowflakeReportData;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Constructor;

/**
 * 基础代码来源 hutool
 *
 * @author zxw
 * @date 2023/4/21 20:28
 */
@Slf4j
public abstract class AbstractSnowflake implements IDataReport {

    /**
     * 初始化时间点
     */
    final long twepoch;
    final long workerId;
    final long dataCenterId;
    /**
     * 允许的时钟回拨毫秒数
     */
    final long timeOffset;

    /**
     * 当在低频模式下时，序号始终为0，导致生成ID始终为偶数<br>
     * 此属性用于限定一个随机上限，在不同毫秒下生成序号时，给定一个随机数，避免偶数问题。<br>
     * 注意次数必须小于{@link SnowflakeConstant#SEQUENCE_MASK}，{@code 0}表示不使用随机数。<br>
     * 这个上限不包括值本身。
     */
    final long randomSequenceLimit;

    /**
     * 自增序号，当高频模式下时，同一毫秒内生成N个ID，则这个序号在同一毫秒下，自增以避免ID重复。
     */
    long sequence = 0L;

    long lastTimestamp;

    /**
     * 是否时钟回拨
     */
    volatile boolean cloakBackwards = false;

    final Class<? extends AbstractBackwards> clockBackwardsHandleClass;

    /**
     * 时钟回避处理
     */
    volatile AbstractBackwards clockBackwardsHandle = null;

    final SnowflakeData snowflakeData;

    public AbstractSnowflake(long lastTimestamp, SnowflakeData snowflakeData) {

        snowflakeData = snowflakeData == null ? SnowflakeData.builder().build() : snowflakeData;
        this.snowflakeData = snowflakeData;
        this.twepoch = (null != snowflakeData.getEpoch()) ? snowflakeData.getEpoch() : SnowflakeConstant.DEFAULT_TWEPOCH;
        this.workerId = Assert.checkBetween(snowflakeData.getWorkerId().longValue(), 0, SnowflakeConstant.MAX_WORKER_ID);
        this.dataCenterId = Assert.checkBetween(snowflakeData.getDataCenterId().longValue(), 0, SnowflakeConstant.MAX_DATA_CENTER_ID);
        this.timeOffset = snowflakeData.getTimeOffset();
        this.randomSequenceLimit = Assert.checkBetween(snowflakeData.getRandomSequenceLimit().longValue(), 0, SnowflakeConstant.SEQUENCE_MASK);
        this.clockBackwardsHandleClass = ClockBackwardsFactory.getHandle(snowflakeData.getBackwardsHandle());
        // 上一次项目停止的时间戳 需要-1毫秒 否则可能出现id重复
        if (lastTimestamp < 0) {
            lastTimestamp = -1L;
        } else {
            // 初始化的时候必须比上次时间多一毫秒 因为你不知道上一毫秒生产了多少个ID 直接使用的话会出现ID重复的情况
            // 如果当前时间小于+1后的lts 则下面会进行休眠1毫秒
            lastTimestamp = lastTimestamp + 1;
        }
        this.lastTimestamp = lastTimestamp;
        if (genTime() < lastTimestamp) {
            // 如果上次的时间大于当前时间 可能之前就发生了时间的借用或者是停止的时候时间回拨了
            // 需要 sleep 1毫秒
            ThreadUtil.safeSleep(1);
        }
        // 注册一下上报
        DataReportHandle.registerReport(this);
    }

    /**
     * 根据Snowflake的ID，获取生成时间
     *
     * @param id snowflake算法生成的id
     * @return 生成的时间
     */
    public long getGenerateDateTime(long id) {
        return (id >> SnowflakeConstant.TIMESTAMP_LEFT_SHIFT & ~(-1L << 41L)) + twepoch;
    }

    /**
     * 下一个ID
     *
     * @return ID
     */
    public synchronized long nextId() {

        return this.doGetNextId();
    }

    /**
     * 获取当前最后一条数据生产的时间戳
     *
     * @return
     */
    public long getLastTimestamp() {

        return this.lastTimestamp;
    }

    /**
     * 获取下一ID
     *
     * @return ID
     */
    abstract long doGetNextId();

    /**
     * 第一次检测到时钟回拨
     *
     * @param currentTimestamp 当前时间戳
     * @return
     */
    void firstCheckClockBackwards(long currentTimestamp) {
        if (this.lastTimestamp - currentTimestamp < timeOffset) {
            // 说明设置过了
            if (this.cloakBackwards) {
                return;
            }
            this.cloakBackwards = true;
            log.debug("lastTimestamp:{},时钟回拨:{}", this.lastTimestamp, (this.lastTimestamp - currentTimestamp));
            // 创建回拨处理器
            try {
                Constructor<? extends AbstractBackwards> constructor = this.clockBackwardsHandleClass.getConstructor(long.class, SnowflakeData.class);
                this.clockBackwardsHandle = constructor.newInstance(this.lastTimestamp, this.snowflakeData.clone());
            } catch (Exception e) {
                log.error("创建时钟回拨处理类异常:", e);
                throw new RuntimeException(e);
            }
        } else {
            // 如果服务器时间有问题(时钟后退) 报错。
            throw new IllegalStateException(StrUtil.format("Clock moved backwards. Refusing to generate id for {}ms", lastTimestamp - currentTimestamp));
        }
    }

    long buildId(long timestamp) {

        return ((timestamp - twepoch) << SnowflakeConstant.TIMESTAMP_LEFT_SHIFT)
                | (dataCenterId << SnowflakeConstant.DATA_CENTER_ID_SHIFT)
                | (workerId << SnowflakeConstant.WORKER_ID_SHIFT)
                | sequence;
    }

    /**
     * 下一个ID（字符串形式）
     *
     * @return ID 字符串形式
     */
    public String nextIdStr() {
        return Long.toString(nextId());
    }

    /**
     * 循环等待下一个时间
     *
     * @param comparisonTime 需要比较的时间
     * @return 下一个时间 -1的话说明等待过程中时间被回拨
     */
    long tilNextMillis(long comparisonTime) {
        long timestamp = genTime();
        // 循环直到操作系统时间戳变化
        while (timestamp == comparisonTime) {
            timestamp = genTime();
        }
        if (timestamp < comparisonTime) {
            // 如果发现新的时间戳比上次记录的时间戳数值小，说明操作系统时间发生了倒退
            this.firstCheckClockBackwards(timestamp);
            return -1;
        } else {
            return timestamp;
        }
    }

    /**
     * 生成时间戳
     *
     * @return 时间戳
     */
    long genTime() {
        return SystemClock.now();
    }

    @Override
    public String report() {

        SnowflakeReportData reportData = SnowflakeReportData.builder()
                .twepoch(twepoch)
                .workerId(workerId)
                .dataCenterId(dataCenterId)
                .sequence(sequence)
                .lastTimestamp(lastTimestamp)
                .build();
        // 重写toString转为json格式
        return reportData.toString();
    }

    @Override
    public String reportPath() {

        return StrUtil.format("snowflake/snowflake_{}_{}.json", this.workerId, this.dataCenterId);
    }
}
